package org.hepeng.workx.spring.cloud.netflix.hystrix.zuul;

import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.netflix.hystrix.strategy.properties.HystrixProperty;
import com.netflix.zuul.context.RequestContext;
import org.joor.Reflect;

import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * do not use this class
 * @author he peng
 */

@Deprecated
public class RequestContextConcurrencyStrategy extends HystrixConcurrencyStrategy {

    private HystrixConcurrencyStrategy delegate;

    public RequestContextConcurrencyStrategy(HystrixConcurrencyStrategy delegate) {
        if (! (delegate instanceof RequestContextConcurrencyStrategy)) {
            this.delegate = delegate;
        }
        init();
    }

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        return getInvoker().getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) {
        return getInvoker().getThreadPool(threadPoolKey, threadPoolProperties);
    }

    @Override
    public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
        return getInvoker().getBlockingQueue(maxQueueSize);
    }

    @Override
    public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
        return getInvoker().getRequestVariable(rv);
    }

    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        DelegatingRequestContextCallable delegatingCallable = new DelegatingRequestContextCallable(callable);
        return getInvoker().wrapCallable(delegatingCallable);
    }

    private void init() {
        HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance()
                .getEventNotifier();
        HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance()
                .getMetricsPublisher();
        HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance()
                .getPropertiesStrategy();
        HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance()
                .getCommandExecutionHook();

        HystrixPlugins.reset();

        HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
        HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
        HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
        HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
        HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
    }

    private HystrixConcurrencyStrategy getInvoker() {
        return Objects.nonNull(this.delegate) ? this.delegate : this;
    }

    private static class DelegatingRequestContextCallable<V> implements Callable<V> {

        private Callable<V> delegate;
        private RequestContext requestContext;

        public DelegatingRequestContextCallable(Callable<V> callable) {
            this(callable , RequestContext.getCurrentContext());
        }

        public DelegatingRequestContextCallable(Callable<V> callable , RequestContext requestContext) {
            this.delegate = callable;
            this.requestContext = requestContext;
        }

        @Override
        public V call() throws Exception {
            ThreadLocal threadLocal = Reflect.on(RequestContext.class).get("threadLocal");
            threadLocal.set(this.requestContext);
            V result;
            try {
                result = this.delegate.call();
            } finally {
                RequestContext.getCurrentContext().unset();
                this.requestContext = null;
            }
            return result;
        }
    }
}
